package red.yhc;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.log.StaticLog;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.logging.LogManager;
import java.util.stream.Collectors;

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {

    private static HttpClient CLIENT;

    private static String AK;

    private static String SK;

    private static String HOST;

    private static String DOMAIN;

    private static String HOST_DOMAIN;

    public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
        //获取参数
        HOST = args[0];
        DOMAIN = args[1];
        AK = args[2];
        SK = args[3];
        HOST_DOMAIN = StrUtil.join(".", HOST, DOMAIN);
        if(!StrUtil.isAllNotBlank(HOST, DOMAIN, AK, SK)) {
            StaticLog.error("parameter error");
            return;
        }
        //当前域名记录值，标记为oldIp
        String oldIp = null;
        try {
            oldIp = InetAddress.getByName(HOST_DOMAIN).getHostAddress();
        }catch (Exception e){
            StaticLog.error(e.getMessage());
        }
        //构建HTTP客户端
        CLIENT = HttpClient.newHttpClient();
        //当前公网IP，标记为newIp
        var newIp = StrUtil.trim(CLIENT.send(HttpRequest.newBuilder().uri(URI.create("https://ipinfo.io/ip")).build(), HttpResponse.BodyHandlers.ofString()).body());
        //比较两个IP的区别
        if(StrUtil.equals(oldIp, newIp)){
            StaticLog.warn("The IP address has not changed and no update is required");
            return;
        }
        //获取域名记录ZID
        var zid = getZID();
        //获取域名解析记录
        var recordId = getRecordID(zid);
        if(recordId.isPresent()) {
            //更新IP解析
            updateDNS(recordId.get(), newIp);
        }else{
            //创建IP解析
            createDNS(zid, newIp);
        }
    }

    //=================================================================

    private static void updateDNS(String recordId, String ip) throws URISyntaxException, IOException, InterruptedException {
        byte[] body = JSONUtil.createObj()
                .set("Host", HOST)
                .set("Line", "default")
                .set("RecordID", recordId)
                .set("TTL", 600)
                .set("Type", "A")
                .set("Value", ip)
                .set("Weight", 1)
                .toString().getBytes();
        var result = request("POST", new HashMap<>(), new HashMap<>(), "UpdateRecord", body);
        if(result.getStr("RecordID").equals(recordId)){
            StaticLog.warn("Update DNS successfully");
        }else{
            StaticLog.error("Update DNS failed");
        }
    }

    private static void createDNS(Integer zid, String ip) throws URISyntaxException, IOException, InterruptedException {
        byte[] body = JSONUtil.createObj()
                .set("Host", HOST)
                .set("Line", "default")
                .set("TTL", 600)
                .set("Type", "A")
                .set("Value", ip)
                .set("ZID", zid)
                .toString().getBytes();
        var result = request("POST", new HashMap<>(), new HashMap<>(), "CreateRecord", body);
        if(result.getStr("Host").equals(HOST)){
            StaticLog.warn("Create DNS successfully");
        }else{
            StaticLog.error("Create DNS failed");
        }
    }

    private static Optional<String> getRecordID(Integer zid) throws URISyntaxException, IOException, InterruptedException {
        var query = new HashMap<String, Object>(){{
            put("ZID", zid);
            put("PageSize", 500);
        }};
        var result = request("GET", query, new HashMap<>(), "ListRecords", new byte[]{});
        var record = result.getJSONArray("Records").stream()
                .map(r -> (JSONObject)r).filter(r -> HOST_DOMAIN.equals(r.getStr("PQDN"))).findFirst().orElse(null);
        return null==record ?Optional.empty() :Optional.of(record.getStr("RecordID"));
    }

    private static Integer getZID() throws URISyntaxException, IOException, InterruptedException {
        var query = new HashMap<String, Object>(1){{
            put("PageSize", 500);
        }};
        var result = request("GET", query, new HashMap<>(), "ListZones", new byte[]{});
        var zone = result.getJSONArray("Zones").stream().map(z -> (JSONObject) z)
                .filter(z -> DOMAIN.equals(z.getStr("ZoneName"))).findFirst().orElse(null);
        assert zone != null;
        return zone.getInt("ZID");
    }

    //=================================================================

    /**
     * sha256非对称加密
     */
    private static byte[] hmacSHA256(byte[] key, String content) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(key, "HmacSHA256"));
            return mac.doFinal(content.getBytes());
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * sha256 hash算法
     */
    private static String hashSHA256(byte[] content) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            return HexUtil.encodeHexStr(md.digest(content));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 发送请求
     */
    private static JSONObject request(String method, Map<String, Object> query, Map<String, String> header, String action, byte[] body) throws URISyntaxException, IOException, InterruptedException {
        // 初始化身份证明
        var credential = Map.of("accessKeyId", AK, "secretKeyId", SK, "service", "DNS", "region", "cn-north-1");

        //计算签名
        var queryList = new ArrayList<>(query.entrySet());
        queryList.sort(Map.Entry.comparingByKey());
        var pairs = queryList.stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.toCollection(ArrayList::new));
        pairs.add("Action=" + action);
        pairs.add("Version=2018-08-01");
        //按参数名称对查询参数进行升序排序
        pairs.sort(Comparator.comparing(s -> s.split("=")[0]));
        // 初始化签名结构
        record RequestParam(byte[] body, String method, Date date, String path, String host, String contentType, List<Map.Entry<String, Object>> queryList) {}
        var requestParam = new RequestParam(body, method, new Date(), "/", "open.volcengineapi.com", "application/json", queryList);
        var uri = new URI("https", requestParam.host, requestParam.path, String.join("&", pairs), null);
        // 接下来开始计算签名
        var formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneId.of("UTC"));
        var zonedDateTime = ZonedDateTime.now(ZoneId.of("UTC"));
        var xDate = formatter.format(zonedDateTime);
        var shortXDate = xDate.substring(0, 8);
        var xContentSha256 = hashSHA256(body);
        //计算
        var headStr = new String[]{"content-type", "host", "x-content-sha256", "x-date"};
        var signedHeadersStr = String.join(";", headStr);
        var headStrSecond = new String[]{"content-type:" + requestParam.contentType, "host:" + requestParam.host, "x-content-sha256:" + xContentSha256, "x-date:" + xDate};
        var preRequestStr = String.join("\n", headStrSecond);
        var preCanonicalRequestStr = new String[]{requestParam.method, requestParam.path, uri.getRawQuery(), preRequestStr, "", signedHeadersStr, xContentSha256};
        var canonicalRequestStr = String.join("\n", preCanonicalRequestStr);
        var hashedCanonicalRequest = hashSHA256(canonicalRequestStr.getBytes());
        var credentialStr = new String[]{shortXDate, credential.get("region"), credential.get("service"), "request"};
        var credentialScope = String.join("/", credentialStr);
        var preStringToSign = new String[]{"HMAC-SHA256", xDate, credentialScope, hashedCanonicalRequest};
        var stringToSign = String.join("\n", preStringToSign);
        var kDate = hmacSHA256(credential.get("secretKeyId").getBytes(), shortXDate);
        var kRegion = hmacSHA256(kDate, credential.get("region"));
        var kService = hmacSHA256(kRegion, credential.get("service"));
        var kSigning = hmacSHA256(kService, "request");
        var signature = HexUtil.encodeHexStr(Objects.requireNonNull(hmacSHA256(kSigning, stringToSign)));
        var authorization = String.format("HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s", credential.get("accessKeyId") + "/" + credentialScope, signedHeadersStr, signature);

        //构建请求头
        var requestBuilder = HttpRequest.newBuilder().uri(uri)
                .header("Content-Type", requestParam.contentType)
                .header("X-Date", xDate)
                .header("X-Content-Sha256", xContentSha256)
                .header("Authorization", authorization);
        header.forEach(requestBuilder::header);
        //构建请求
        var request = switch (method.toUpperCase()) {
            case "POST" -> requestBuilder.POST(HttpRequest.BodyPublishers.ofByteArray(body)).build();
            case "GET" -> requestBuilder.GET().build();
            default -> throw new UnsupportedOperationException();
        };
        //发送请求
        var response = CLIENT.send(request, HttpResponse.BodyHandlers.ofByteArray());
        //解析结果
        var entries = JSONUtil.parseObj(new String(response.body(), StandardCharsets.UTF_8));
        if(!entries.containsKey("Result")){
            RuntimeException error = new RuntimeException(entries.getJSONObject("ResponseMetadata").getJSONObject("Error").getStr("Message"));
            StaticLog.error(error, "错误");
            throw error;
        }
        return entries.getJSONObject("Result");
    }

}
